//
//   Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
//   This program is free software; you can redistribute it and/or modify
//   it under the terms of the GNU General Public License version 2 as
//   published by the Free Software Foundation.
//
//   This program is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU General Public License version 2 for more details.
//
//   You should have received a copy of the GNU General Public License
//   version 2 along with this program; if not, write to the
//   Free Software Foundation, Inc.,
//   51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
//

#include "inputdialog.h"
#include "dialoghelpers.h"
#include "inputbox.h"
#include <QGridLayout>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QLabel>
#include <QPushButton>
#include <QSettings>
#include <QSpinBox>
#include <QTabWidget>
#include <QVBoxLayout>

template<class W>
static W * addWidget(QGridLayout *l, W *w, int row, int col) {
	l->addWidget(w, row, col);
	return w;
}

template<class L>
static L * addLayout(QGridLayout *parent, L *l, int row, int col) {
	parent->addLayout(l, row, col);
	return l;
}

static QSpinBox * addFppBox(QLayout *parent, int fpp) {
	QSpinBox *const box = addWidget(parent, new QSpinBox);
	box->setValue(fpp);
	box->setMinimum(1);
	box->setMaximum(9);
	box->setSuffix(" fpp");
	return box;
}

InputDialog::InputDialog(auto_vector<Button> &buttons, QWidget *parent)
: QDialog(parent)
, buttons_(buttons)
, inputBoxes_(buttons_.size() * 2)
, fppBoxes_(  buttons_.size() * 2)
, config_(    buttons_.size() * 2)
{
	setWindowTitle(tr("Input Settings"));

	QVBoxLayout *const mainLayout = new QVBoxLayout(this);
	QTabWidget *const tabw = addWidget(mainLayout, new QTabWidget);
	QHBoxLayout *const okCancelLayout = addLayout(mainLayout, new QHBoxLayout,
	                                              Qt::AlignBottom | Qt::AlignRight);
	QPushButton *const okButton     = addWidget(okCancelLayout, new QPushButton(tr("OK")));
	QPushButton *const cancelButton = addWidget(okCancelLayout, new QPushButton(tr("Cancel")));
	okButton->setDefault(true);
	connect(okButton,     SIGNAL(clicked()), this, SLOT(accept()));
	connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject()));

	for (std::size_t i = 0; i < buttons_.size(); ++i) {
		QString const label = buttons_[i]->label();
		if (!label.isEmpty()) {
			QString const category = buttons_[i]->category();
			int j = tabw->count() - 1;
			while (j >= 0 && tabw->tabText(j) != category)
				--j;

			if (j < 0) {
				QWidget *const w = new QWidget;
				j = tabw->addTab(w, category);

				QBoxLayout *boxl = new QVBoxLayout(w);
				boxl->addLayout(new QGridLayout);
				boxl->setAlignment(Qt::AlignTop);
			}

			int const defaultFpp = buttons_[i]->defaultFpp();
			QGridLayout *const gLayout =
				static_cast<QGridLayout *>(tabw->widget(j)->layout()->itemAt(0));
			gLayout->addWidget(new QLabel(label + ':'), i, 0);

			if (defaultFpp) {
				QHBoxLayout *hLayout = addLayout(gLayout, new QHBoxLayout, i, 1);
				inputBoxes_[i * 2    ] = addWidget(hLayout, new InputBox);
				  fppBoxes_[i * 2    ] = addFppBox(hLayout, defaultFpp);

				hLayout = addLayout(gLayout, new QHBoxLayout, i, 2);
				inputBoxes_[i * 2 + 1] = addWidget(hLayout, new InputBox);
				  fppBoxes_[i * 2 + 1] = addFppBox(hLayout, defaultFpp);
			} else {
				inputBoxes_[i * 2    ] = addWidget(gLayout, new InputBox, i, 1);
				inputBoxes_[i * 2 + 1] = addWidget(gLayout, new InputBox, i, 2);
			}

			QPushButton *const clearButton =
				addWidget(gLayout, new QPushButton(tr("Clear")), i, 3);
			connect(clearButton, SIGNAL(clicked()), inputBoxes_[i * 2 + 1], SLOT(clear()));
			connect(inputBoxes_[i * 2 + 1], SIGNAL(redundantClear()),
			        inputBoxes_[i * 2    ], SLOT(clear()));
		}
	}

	for (int tabi = 0; tabi < tabw->count(); ++tabi) {
		QWidget *const w = tabw->widget(tabi);
		std::size_t i = 0;
		while (i < inputBoxes_.size()
				&& (!inputBoxes_[i]
				    || inputBoxes_[i]->parentWidget() != w)) {
			i += 2;
		}

		while (i < inputBoxes_.size()) {
			std::size_t j = i + 2;
			while (j < inputBoxes_.size()
					&& (!inputBoxes_[j]
					    || inputBoxes_[j]->parentWidget() != w)) {
				j += 2;
			}

			if (j < inputBoxes_.size()) {
				inputBoxes_[i    ]->setNextFocus(inputBoxes_[j    ]);
				inputBoxes_[i + 1]->setNextFocus(inputBoxes_[j + 1]);
			} else {
				inputBoxes_[i    ]->setNextFocus(okButton);
				inputBoxes_[i + 1]->setNextFocus(okButton);
			}

			i = j;
		}
	}

	QSettings settings;
	settings.beginGroup("input");

	for (std::size_t i = 0; i < buttons_.size(); ++i) {
		int const defaultKey = buttons_[i]->defaultKey();
		int const defaultAltKey = buttons_[i]->defaultAltKey();
		int const defaultFpp = buttons_[i]->defaultFpp();
		QString const label = buttons_[i]->label();
		if (!label.isEmpty()) {
			QString const category = buttons_[i]->category();
			config_[i * 2    ].event.id =
				settings.value(category + label + "Key1", defaultKey).toUInt();
			config_[i * 2    ].event.value =
				settings.value(category + label + "Value1",
				                 defaultKey
				               ? InputBox::value_kbd
				               : InputBox::value_null).toInt();
			config_[i * 2    ].fpp = defaultFpp
				? settings.value(category + label + "Fpp1", defaultFpp).toInt()
				: 0;

			config_[i * 2 + 1].event.id =
				settings.value(category + label + "Key2", defaultAltKey).toUInt();
			config_[i * 2 + 1].event.value =
				settings.value(category + label + "Value2",
				                 defaultAltKey
				               ? InputBox::value_kbd
				               : InputBox::value_null).toInt();
			config_[i * 2 + 1].fpp = defaultFpp
				? settings.value(category + label + "Fpp2", defaultFpp).toInt()
				: 0;
		} else {
			config_[i * 2    ].event.id = defaultKey;
			config_[i * 2    ].event.value = defaultKey
			                               ? InputBox::value_kbd
			                               : InputBox::value_null;
			config_[i * 2    ].fpp = defaultFpp;

			config_[i * 2 + 1].event.id = defaultAltKey;
			config_[i * 2 + 1].event.value = defaultAltKey
			                               ? InputBox::value_kbd
			                               : InputBox::value_null;
			config_[i * 2 + 1].fpp = defaultFpp;
		}
	}

	settings.endGroup();
	removeProblemValues();
	restore();
	resetMapping();
}

InputDialog::~InputDialog() {
	QSettings settings;
	settings.beginGroup("input");

	for (std::size_t i = 0; i < buttons_.size(); ++i) {
		QString const label = buttons_[i]->label();
		if (!label.isEmpty()) {
			QString const category = buttons_[i]->category();
			settings.setValue(category + label + "Key1",   config_[i * 2    ].event.id);
			settings.setValue(category + label + "Value1", config_[i * 2    ].event.value);
			settings.setValue(category + label + "Key2",   config_[i * 2 + 1].event.id);
			settings.setValue(category + label + "Value2", config_[i * 2 + 1].event.value);
			if (buttons_[i]->defaultFpp()) {
				settings.setValue(category + label + "Fpp1", config_[i * 2    ].fpp);
				settings.setValue(category + label + "Fpp2", config_[i * 2 + 1].fpp);
			}
		}
	}

	settings.endGroup();
}

void InputDialog::resetMapping() {
	{
		Mutual<KeyMapping>::Locked lkm(keyMapping_);
		lkm->map.clear();
		lkm->rapidvec.clear();

		for (std::size_t i = 0; i < config_.size(); ++i) {
			if (config_[i].event.value == InputBox::value_kbd) {
				lkm->map.insert(std::make_pair(
					config_[i].event.id,
					MappedKey(buttons_[i >> 1], config_[i].fpp)));
			}
		}
	}

	{
		Mutual<JoyMapping>::Locked ljm(joyMapping_);
		ljm->map.clear();
		ljm->rapidvec.clear();

		for (std::size_t i = 0; i < config_.size(); ++i) {
			if (config_[i].event.value != InputBox::value_null
					&& config_[i].event.value != InputBox::value_kbd) {
				ljm->map.insert(std::make_pair(
					config_[i].event.id,
					MappedJoy(buttons_[i >> 1], config_[i].event.value,
					          config_[i].fpp)));
			}
		}
	}
}

void InputDialog::store() {
	for (std::size_t i = 0; i < inputBoxes_.size(); ++i) {
		if (inputBoxes_[i])
			config_[i].event = inputBoxes_[i]->data();
	}

	for (std::size_t i = 0; i < fppBoxes_.size(); ++i) {
		if (fppBoxes_[i])
			config_[i].fpp = fppBoxes_[i]->value();
	}

	resetMapping();
}

bool InputDialog::validateInputBindings() {
	// also checks for shift
	bool shiftBound = false;
	for(std::size_t i = 0; i < inputBoxes_.size(); ++i) {
		if(inputBoxes_[i] && inputBoxes_[i]->data().value == InputBox::value_kbd && inputBoxes_[i]->data().id == Qt::Key_Shift) {
			shiftBound = true;
		}
		for(std::size_t j = 0; j < i / 2 * 2; ++j) {
			if(inputBoxes_[i] && inputBoxes_[j]
					&& !inputBoxes_[i]->isEmpty()
					&& !inputBoxes_[j]->isEmpty()
					&& inputBoxes_[i]->data().value == inputBoxes_[j]->data().value
					&& inputBoxes_[i]->data().id == inputBoxes_[j]->data().id) {
				char buffer[512];
				sprintf(buffer, "Keybind for %s and %s are equal. You may not bind the same keyboard/joystick input to more than one Gameboy input. Please adjust your inputs and try again.", buttons_[i/2]->label().toStdString().c_str(), buttons_[j/2]->label().toStdString().c_str());
				QMessageBox::critical(
					this,
					tr("Input Binding Error"),
					buffer,
					QMessageBox::Ok);
				return false;
			}
		}
	}
	if(shiftBound) {
		QMessageBox::StandardButton button = QMessageBox::warning(
			this,
			tr("Input Binding Warning"),
			("Please note that using SHIFT as an input in combination with any key that it modifies may lead to buttons still being considered held after you release them. For best results, please choose another input button.\n\nTo continue on with SHIFT still bound press OK, if you want to change it press Cancel."),
			QMessageBox::Ok | QMessageBox::Cancel);
		return QMessageBox::Ok == button;
	}
	return true;
}

void InputDialog::removeProblemValues() {
	for(std::size_t i = 0; i < inputBoxes_.size(); ++i) {
		if(inputBoxes_[i]) {
			if(config_[i].event.value == InputBox::value_kbd && config_[i].event.id == 0) {
				// replace with null to avoid forceclose on certain keyboard layouts
				config_[i].event.value = InputBox::value_null;
			}
			// silently drop any duplicate binds that make it this far
			for(std::size_t j = 0; j < i; ++j) {
				if(inputBoxes_[j]
						&& config_[i].event.value != InputBox::value_null
						&& config_[j].event.value != InputBox::value_null
						&& config_[i].event.value == config_[j].event.value
						&& config_[i].event.id == config_[j].event.id) {
					config_[i].event.id = 0;
					config_[i].event.value = InputBox::value_null;
				}
			}
		}
	}
}

void InputDialog::restore() {
	for (std::size_t i = 0; i < inputBoxes_.size(); ++i) {
		if (inputBoxes_[i])
			inputBoxes_[i]->setData(config_[i].event);
	}

	for (std::size_t i = 0; i < fppBoxes_.size(); ++i) {
		if (fppBoxes_[i])
			fppBoxes_[i]->setValue(config_[i].fpp);
	}
}

void InputDialog::setJoystickThreshold(int threshold) {
	for (std::size_t i = 0; i < inputBoxes_.size(); ++i) {
		if (inputBoxes_[i])
			inputBoxes_[i]->setJoystickThreshold(threshold);
	}

}

template<class AutoPressVec>
static void setButtonPressed(AutoPressVec &v, InputDialog::Button *const button, int const fpp) {
	if (fpp) {
		for (typename AutoPressVec::iterator it = v.begin(); it != v.end(); ++it) {
			if (it->button == button && it->fpp == fpp)
				return;
		}

		v.push_back(typename AutoPressVec::value_type(button, fpp, 0));
	}

	button->pressed();
}

template<class AutoPressVec>
static void unsetButtonPressed(AutoPressVec &v, InputDialog::Button *const button, int const fpp) {
	if (fpp) {
		for (typename AutoPressVec::iterator it = v.begin(); it != v.end(); ++it) {
			if (it->button == button && it->fpp == fpp) {
				v.erase(it);
				break;
			}
		}
	}

	button->released();
}

void InputDialog::keyPress(QKeyEvent const *const e) {
	if (Mutual<KeyMapping>::TryLocked const &lm = keyMapping_) {
		for (std::pair<Kmap::iterator, Kmap::iterator> r
				= lm->map.equal_range(e->key()); r.first != r.second; ++r.first) {
			setButtonPressed(lm->rapidvec, r.first->second.button,
			                 r.first->second.fpp);
		}
	}
}

void InputDialog::keyRelease(QKeyEvent const *const e) {
	if (Mutual<KeyMapping>::TryLocked const &lm = keyMapping_) {
		for (std::pair<Kmap::iterator, Kmap::iterator> r
				= lm->map.equal_range(e->key()); r.first != r.second; ++r.first) {
			unsetButtonPressed(lm->rapidvec, r.first->second.button,
			                   r.first->second.fpp);
		}
	}
}

void InputDialog::joystickEvent(SDL_Event const &ev) {
	if (Mutual<JoyMapping>::TryLocked const &lm = joyMapping_) {
		for (std::pair<Jmap::iterator, Jmap::iterator> r
				= lm->map.equal_range(ev.id); r.first != r.second; ++r.first) {
			if ((ev.value & r.first->second.mask) == r.first->second.mask) {
				  setButtonPressed(lm->rapidvec, r.first->second.button,
				                   r.first->second.fpp);
			} else {
				unsetButtonPressed(lm->rapidvec, r.first->second.button,
				                   r.first->second.fpp);
			}
		}
	}
}

void InputDialog::clearKeyPresses() {
    if (Mutual<KeyMapping>::TryLocked const &lm = keyMapping_) {
        for(Kmap::iterator r = lm->map.begin(); r != lm->map.end(); ++r) {
            unsetButtonPressed(lm->rapidvec, (*r).second.button, (*r).second.fpp);
        }
    }
}

template<class AutoPressVec>
static void doConsumeAutoPress(AutoPressVec &v) {
	for (typename AutoPressVec::iterator it = v.begin(); it != v.end(); ++it) {
		if (it->fcnt == 0) {
			it->fcnt = it->fpp;
			it->button->released();
		} else if (--it->fcnt == 0)
			it->button->pressed();
	}
}

void InputDialog::consumeAutoPress() {
	if (Mutual<KeyMapping>::TryLocked const &lm = keyMapping_)
		doConsumeAutoPress(lm->rapidvec);
	if (Mutual<JoyMapping>::TryLocked const &lm = joyMapping_)
		doConsumeAutoPress(lm->rapidvec);
}

void InputDialog::done(int r) {
	if(QDialog::Accepted == r) {
		// ok pressed
		if(validateInputBindings()) {
			store();
			removeProblemValues();
			restore();
			QDialog::done(r);
			return;
		}
	}
	else {
		// cancel
		restore();
		QDialog::done(r);
		return;
	}
}
